package org.internna.expensetracker.mvc;

import java.util.ArrayList;
import java.util.Set;
import java.util.Date;
import java.util.List;
import java.util.TreeSet;
import java.util.SortedSet;
import java.math.BigDecimal;
import java.text.SimpleDateFormat;
import org.internna.expensetracker.model.Payee;
import org.internna.expensetracker.model.Account;
import org.internna.expensetracker.cache.CacheStore;
import org.internna.expensetracker.model.AccountType;
import org.internna.expensetracker.model.Subcategory;
import org.internna.expensetracker.services.AccountService;
import org.internna.expensetracker.model.AccountTransaction;
import org.internna.expensetracker.model.security.UserDetails;
import org.internna.expensetracker.model.FinancialInstitution;
import org.springframework.ui.ModelMap;
import org.springframework.util.CollectionUtils;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.InitBinder;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.propertyeditors.CustomDateEditor;

import static org.springframework.util.StringUtils.hasText;

@Controller
@RequestMapping("/financial/accounts/**")
public final class AccountController {

	@Autowired private CacheStore cache;
    @Autowired private AccountService accountService;

    @InitBinder
    public void initBinder(WebDataBinder binder) {
        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
        binder.registerCustomEditor(Date.class, new CustomDateEditor(dateFormat, false));
    }

    @RequestMapping
    public String index(ModelMap modelMap) {
        modelMap.addAttribute("all", Boolean.TRUE);
        SortedSet<Account> accounts = new TreeSet<Account>();
        UserDetails user = UserDetails.findCurrentUser();
        Set<Account> userAccounts = user.getAccounts();
        if (CollectionUtils.isEmpty(userAccounts)) {
			return CollectionUtils.isEmpty(user.getInstitutions()) ? "administration/createInstitution" : createForm(modelMap);
        }
        accounts.addAll(userAccounts);
    	modelMap.addAttribute("accounts", accounts);
        return "accounts/index";
    }

    @RequestMapping("favorites")
    public String favorites(ModelMap modelMap) {
        modelMap.addAttribute("all", Boolean.FALSE);
    	modelMap.addAttribute("accounts", UserDetails.findCurrentUser().getFavoriteAccounts());
        return "accounts/favorites";
    }

    @RequestMapping("{id}")
    public String details(@PathVariable Long id, ModelMap modelMap) {
        return details(Account.findAccount(id), modelMap);
    }

    protected String details(Account account, ModelMap modelMap) {
    	if (account != null) {
            UserDetails user = UserDetails.findCurrentUser();
            if (account.belongsTo(user)) {
                modelMap.addAttribute("account", account);
                modelMap.addAttribute("transactions", new TreeSet<AccountTransaction>(account.getTransactions()));
            }
        }
        return account.isCreditCard() ? "accounts/detailscc" : account.isBankAccount() ? "accounts/details" : "accounts/investment";
    }

    @RequestMapping("create")
    public String createForm(ModelMap modelMap) {
    	UserDetails user = UserDetails.findCurrentUser();
    	Set<FinancialInstitution> institutions = new TreeSet<FinancialInstitution>(user.getInstitutions());
    	if (CollectionUtils.isEmpty(institutions)) {
    		return "administration/createInstitution";
    	}
		modelMap.addAttribute("institutions", institutions);
    	modelMap.addAttribute("types", AccountType.findAllAccountTypes());
        return "accounts/create";
    }

    @RequestMapping(value = "create", method = RequestMethod.POST)
    public String create(Account account, ModelMap modelMap) {
        accountService.createAccount(account);
        cache.invalidate(account.getOwner());
        return index(modelMap);
    }

    @RequestMapping("add")
    public String addTransaction(ModelMap modelMap) {
        fillTransactionModel(modelMap);
        return "accounts/add";
    }

    @RequestMapping("add/{account}")
    public String addTransaction(@PathVariable Long account, ModelMap modelMap) {
        UserDetails user = fillTransactionModel(modelMap);
        fillProvidedAccount(account, user, modelMap);
        return "accounts/add";
    }

    protected UserDetails fillTransactionModel(ModelMap modelMap) {
        UserDetails user = fillTransferTransactionModel(modelMap);
        modelMap.addAttribute("payees", new TreeSet<Payee>(user.getPayees()));
        modelMap.addAttribute("categories", new TreeSet<Subcategory>(Subcategory.findSelectableCategories(user)));
        return user;
    }

    protected UserDetails fillTransferTransactionModel(ModelMap modelMap) {
        UserDetails user = UserDetails.findCurrentUser();
        List<Account> accounts = Account.findOpenAccounts(user);
    	modelMap.addAttribute("accounts", accounts);
        String currency = "EUR";
        if (!CollectionUtils.isEmpty(accounts)) {
            currency = accounts.get(0).getCurrency();
        }
        modelMap.addAttribute("currency",  currency);
        return user;
    }

    @SuppressWarnings("unchecked")
	protected void fillProvidedAccount(Long account, UserDetails user, ModelMap modelMap) {
        Account loaded = Account.findAccount(account);
        if ((loaded != null) && (loaded.belongsTo(user))) {
            modelMap.addAttribute("account", loaded);
            modelMap.addAttribute("currency",  loaded.getCurrency());
            List<Account> accounts = new ArrayList<Account>((List<Account>) modelMap.get("accounts"));
            accounts.remove(loaded);
            modelMap.addAttribute("accounts", accounts);
        }
    }

    @RequestMapping(value = "add", method = RequestMethod.POST)
    public String addTransaction(AccountTransaction transaction, ModelMap modelMap) {
        long accountId = accountService.addTransaction(transaction);
        cache.invalidate(transaction.getAccount().getOwner());
        return details(accountId, modelMap);
    }

    @RequestMapping("transfer")
    public String transfer(ModelMap modelMap) {
        fillTransferTransactionModel(modelMap);
        return "accounts/transfer";
    }

    @RequestMapping("transfer/{account}")
    public String transfer(@PathVariable Long account, ModelMap modelMap) {
        UserDetails user = fillTransferTransactionModel(modelMap);
        fillProvidedAccount(account, user, modelMap);
        return "accounts/transfer";
    }

    @RequestMapping(value = "transfer", method = RequestMethod.POST)
    public String transfer(Long origin, Long target, Date operationDate, BigDecimal amount, BigDecimal chargeAmount, double rate, String memo, ModelMap modelMap) {
    	cache.invalidate(UserDetails.findCurrentUser());
        accountService.transferMoney(origin, target, operationDate, amount, chargeAmount, rate, memo);
        return details(origin, modelMap);
    }

    @RequestMapping("balance/{accountId}")
    public String balance(@PathVariable Long accountId, ModelMap modelMap) {
    	Account account = Account.findAccount(accountId);
        if (account != null) {
            UserDetails user = UserDetails.findCurrentUser();
            if (account.belongsTo(user) && account.isCreditCard()) {
            	modelMap.addAttribute("account", account);
            	modelMap.addAttribute("origin", user.getBankAccounts());
            	modelMap.addAttribute("transactions", AccountTransaction.findUnreconciledTransactions(account));
            }
        }
        return "accounts/balance";
    }

    @RequestMapping(value = "pay-balance", method = RequestMethod.POST)
    public String balance(Long accountId, Long originId, String transactions, Date operationDate, ModelMap modelMap) {
    	if (hasText(transactions)) {
    		Account origin = Account.findAccount(originId);
    		Account account = Account.findAccount(accountId);
    		if ((account != null) && (origin != null)) {
	    		UserDetails user = UserDetails.findCurrentUser();
	    		if (account.belongsTo(user) && origin.belongsTo(user)) {
	    			cache.invalidate(user);
		    		accountService.balance(user, account, origin, operationDate, transactions.split("-"));
	    		}
    		}
    	}
    	return index(modelMap);
    }

    @RequestMapping("view/{id}")
    public String view(@PathVariable Long id, ModelMap modelMap) {
    	Account account = Account.findAccount(id);
    	UserDetails user = UserDetails.findCurrentUser();
    	modelMap.addAttribute("institutions", new TreeSet<FinancialInstitution>(user.getInstitutions()));
    	if (account.belongsTo(user)) {
    		modelMap.addAttribute("account", account);
    	}
    	return "accounts/view";
    }

    @RequestMapping(value = "edit", method = RequestMethod.POST)
    public String edit(Account account, ModelMap modelMap) {
    	UserDetails user = UserDetails.findCurrentUser();
    	Account loaded = Account.findAccount(account.getId());
    	if (loaded.belongsTo(user)) {
    		cache.invalidate(user);
			loaded.setIsbn(account.getIsbn());
	    	loaded.setName(account.getName());
	    	loaded.setLocale(account.getLocale());
	    	loaded.setOpened(account.getOpened());
	    	loaded.setFavorite(account.getFavorite());
	    	loaded.setDescription(account.getDescription());
	    	loaded.setAccountNumber(account.getAccountNumber());
	    	loaded.setInitialBalance(account.getInitialBalance());
	    	loaded.setHeldAt(FinancialInstitution.findFinancialInstitution(account.getHeldAt().getId()));
	    	loaded.merge();
    	}
    	return index(modelMap);
    }

    @RequestMapping("view-transaction/{id}")
    public String viewTransaction(@PathVariable Long id, ModelMap modelMap) {
    	String jsp = "accounts/view-transaction";
    	UserDetails user = fillTransactionModel(modelMap);
    	AccountTransaction accountTransaction = AccountTransaction.findAccountTransaction(id);
    	if ((accountTransaction != null) &&  (accountTransaction.isInvestmentTransaction())) {
    		jsp = "redirect:/financial/investments/view/" + accountTransaction.getInvestment().getId();
    	} else if ((accountTransaction != null) && (accountTransaction.getAccount().belongsTo(user))) {
    		modelMap.addAttribute("transaction", accountTransaction);
    	}
    	return jsp;
    }

    @RequestMapping(value = "edit-transaction", method = RequestMethod.POST)
    public String editTransaction(AccountTransaction transaction, ModelMap modelMap) {
    	UserDetails user = UserDetails.findCurrentUser();
    	AccountTransaction loaded = AccountTransaction.findAccountTransaction(transaction.getId());
    	if (loaded.getAccount().belongsTo(user)) {
    		cache.invalidate(user);
	    	loaded.setMemo(transaction.getMemo());
	    	loaded.setAmount(transaction.getAmount());
	    	loaded.setOperationDate(transaction.getOperationDate());
	    	loaded.setReferenceNumber(transaction.getReferenceNumber());
	    	loaded.setPayee(Payee.findPayee(transaction.getPayee().getId()));
	    	loaded.setSubcategory(Subcategory.findSubcategory(transaction.getSubcategory().getId()));
	    	loaded.merge();
    	}
    	return details(loaded.getAccount().getId(), modelMap);
    }

    @RequestMapping("remove/{id}")
    public String remove(@PathVariable Long id, ModelMap modelMap) {
    	Account account = null;
    	AccountTransaction transaction  = AccountTransaction.findAccountTransaction(id);
    	if (transaction != null) {
    		account = transaction.getAccount();
    		if (account.belongsTo(UserDetails.findCurrentUser())) {
    			transaction.remove();
    		}
    	}
    	return details(account, modelMap);
    }

}
